"""
字段审核服务

实现字段审核的主流程：
1. 根据audit_type查找规则配置
2. 解析用户附件（检查哈希是否已解析过）
3. 如果未解析，则走字段提取和溯源流程
4. 遍历审核规则，构建LLM请求参数
5. 调用LLM审核agent
6. 封装返回结果
"""

import asyncio
import json
import re
import time
import uuid
from typing import List, Dict, Any, Optional, Set, Tuple
from loguru import logger

from config.llm_config import LLMConfig
from src.services.llm.client import LLMClient, AuditAgent
from src.services.rule_config_service import RuleConfigService
from src.services.extraction.service import ExtractionService
from src.services.classification_service import classification_service
from src.services.task_store import get_task_store
from src.schemas.task import TaskType, TaskStatus
from src.schemas.audit import (
    AuditRequest,
    AuditData,
    AuditRuleResult,
    AuditSummary,
    AuditResult as AuditResultEnum,
    AuditStatus,
    AuditType as AuditTypeEnum,
    RelatedFieldInfo,
    UploadedFileInfo,
    ConfirmedField,
    AuditFileInput
)
from src.schemas.extraction import ExtractionRequest, ExtractionFileInput, FieldExtractionResult
from src.schemas.file import FILE_TYPE_LABELS, LABEL_TO_FILE_TYPE, FileType
from src.schemas.rule_config import AuditRule, AuditType, MappingType
from src.utils.extraction_type_mapper import map_extraction_type_to_business_type
from src.utils.file_hash import FileHasher
from src.utils.rag_handler import RagHandler


class AuditService:
    """字段审核服务"""
    
    def __init__(self):
        """初始化审核服务"""
        self.rule_service = RuleConfigService()
        self.extraction_service = ExtractionService()
        self.task_store = get_task_store()
        
        # LLM配置
        self.llm_config = LLMConfig()
        self.concurrency_config = self.llm_config.get_concurrency_config()
        
        # RAG处理器（用于处理大文件审核）
        self.rag_handler = RagHandler()
    
    async def audit_fields(
        self,
        request: AuditRequest,
        enable_cache: bool = True
    ) -> AuditData:
        """
        执行字段审核
        
        主流程：
        0. 文件分类（将中台类型代码映射到系统FileType）
        1. 根据audit_type查找规则配置
        2. 计算附件哈希，尝试从缓存中获取提取结果
        3. 如果没有缓存，走一次字段提取和溯源流程
        4. 遍历审核规则，构建LLM请求参数
        5. 调用LLM审核agent
        6. 封装返回结果
        
        Args:
            request: 审核请求
            enable_cache: 是否启用缓存查询（默认True）
            
        Returns:
            AuditData: 审核响应数据
        """
        start_time = time.time()
        
        # 0. 文件分类：将中台类型代码映射到系统FileType，同时缓存解析结果
        logger.info("开始文件分类，将中台类型映射到系统FileType")
        parsed_files_cache = await self._classify_files(request)
        
        # 生成任务ID
        task_id = request.task_id or request.request_id
        task_created = False
        
        # 计算附件哈希（用于缓存查询）
        files_hash = FileHasher.compute_files_hash(
            files=request.files,
            extraction_type=request.audit_type,
            rule_config_id=request.rule_config_id
        )
        
        try:
            # 创建任务（如果有task_id或request_id）
            if task_id:
                await self.task_store.create_task(
                    task_id=task_id,
                    task_type=TaskType.AUDIT,
                    context={
                        "audit_type": request.audit_type,
                        "rule_config_id": request.rule_config_id
                    },
                    files_hash=files_hash
                )
                task_created = True
                await self.task_store.update_task(task_id, status=TaskStatus.PROCESSING, progress=0.0)
            
            # 1. 加载规则配置
            config = await self._load_rule_config(request)
            logger.info(f"使用规则配置: {config.id} ({config.name})")
            
            # 更新任务上下文，保存总规则数（用于任务查询时显示进度）
            if task_created and task_id:
                total_rules = len(config.audit_rules)
                await self.task_store.update_task(
                    task_id,
                    context={
                        "audit_type": request.audit_type,
                        "rule_config_id": config.id,
                        "total_rules": total_rules
                    }
                )
            
            # 2. 获取提取结果（缓存或新提取），复用分类阶段的解析结果
            logger.info("开始获取附件提取结果")
            extraction_results, extraction_results_list, parsed_files = await self._get_extraction_results(
                request=request,
                config_id=config.id,
                enable_cache=enable_cache,
                files_hash=files_hash,
                parsed_files_cache=parsed_files_cache
            )
            logger.info(f"获取到 {len(extraction_results)} 个字段的提取结果")
            
            # 构建材料名称到文件类型的映射
            material_lookup = self._build_material_lookup(request.files)
            
            # 解析全文所需的文件
            parsed_files = parsed_files or {}
            required_file_types = self._get_required_file_types(
                audit_rules=config.audit_rules,
                material_lookup=material_lookup,
                parsed_files=parsed_files
            )
            
            missing_file_types = {
                file_type for file_type in required_file_types
                if not parsed_files.get(file_type) or not parsed_files[file_type].get("md_content")
            }
            
            if missing_file_types:
                logger.info(f"检测到需要额外解析全文的文件类型: {missing_file_types}")
                additional_parsed = await self._parse_required_files(
                    request=request,
                    missing_file_types=missing_file_types
                )
                
                if additional_parsed:
                    for file_type, data in additional_parsed.items():
                        if file_type in parsed_files:
                            existing = parsed_files[file_type]
                            existing_names = set(existing.get("file_names", []))
                            new_names = data.get("file_names", [])
                            existing["file_names"] = list(existing_names.union(new_names))
                            if not existing.get("md_content"):
                                existing["md_content"] = data.get("md_content", "")
                            if not existing.get("file_label"):
                                existing["file_label"] = data.get("file_label")
                        else:
                            parsed_files[file_type] = data
                else:
                    logger.warning("未能解析缺失的全文文件，将以空内容继续审核")
            else:
                logger.info("无需额外解析文件，复用提取阶段的解析结果")
            
            # 3. 构建用户确认字段映射
            confirmed_fields_map = self._build_confirmed_fields_map(request.confirmed_fields)
            
            # 4. 审核所有规则
            logger.info(f"开始审核 {len(config.audit_rules)} 个规则")
            audit_results = await self._audit_all_rules(
                audit_rules=config.audit_rules,
                extraction_results=extraction_results,
                extraction_results_list=extraction_results_list,
                confirmed_fields_map=confirmed_fields_map,
                parsed_files=parsed_files,
                material_lookup=material_lookup,
                request=request,
                task_id=task_id,
                task_created=task_created
            )
            
            # 5. 添加人工审核项（未被自动审核覆盖的字段）
            logger.info("检查需人工审核的字段")
            audit_results = self._add_manual_review_items(
                confirmed_fields=request.confirmed_fields,
                audit_results=audit_results,
                extraction_results_list=extraction_results_list
            )
            
            # 6. 统计结果
            passed_count = sum(1 for r in audit_results if r.audit_result == AuditResultEnum.PASSED)
            failed_count = sum(1 for r in audit_results if r.audit_result == AuditResultEnum.FAILED)
            pending_count = sum(1 for r in audit_results if r.audit_result == AuditResultEnum.PENDING)
            manual_review_count = sum(1 for r in audit_results if r.audit_result == AuditResultEnum.MANUAL_REVIEW)
            
            summary = AuditSummary(
                total=len(audit_results),
                passed=passed_count,
                failed=failed_count,
                pending=pending_count,
                manual_review=manual_review_count
            )
            
            processing_time = int((time.time() - start_time) * 1000)
            
            response = AuditData(
                request_id=request.request_id or task_id or f"req_{int(time.time() * 1000)}",
                task_id=task_id if task_created else None,
                status="completed",
                audit_type=request.audit_type,
                confirmed_fields=request.confirmed_fields,
                audit_results=audit_results,
                summary=summary,
                processing_time_ms=processing_time
            )
            
            # 更新任务状态为完成
            if task_created and task_id:
                await self.task_store.update_task(
                    task_id,
                    status=TaskStatus.COMPLETED,
                    progress=1.0,
                    result=response.model_dump()
                )
            
            return response
            
        except Exception as e:
            logger.error(f"字段审核失败: {e}", exc_info=True)
            
            # 更新任务状态为失败
            if task_created and task_id:
                await self.task_store.update_task(
                    task_id,
                    status=TaskStatus.FAILED,
                    error_message=str(e)
                )
            
            raise
    
    async def _classify_files(self, request: AuditRequest) -> Dict[str, Any]:
        """
        对请求中的文件进行分类，将中台类型代码映射到系统FileType
        
        优化策略：
        1. 先处理可直接映射的文件（快速通道）
        2. 批量解析需要LLM分类的文件
        3. 并行调用LLM进行分类
        
        Args:
            request: 审核请求（会直接修改request.files中的file_type）
            
        Returns:
            Dict[str, Any]: 已解析文件的缓存字典，key为file_url，value为parse_result
                用于后续提取阶段复用，避免重复解析
        """
        from src.services.classification_service import FileClassificationService
        
        # 第一阶段：快速处理可直接映射的文件
        files_need_llm = []  # 需要LLM分类的文件列表
        
        for file_input in request.files:
            original_type = file_input.file_type
            
            # 检查是否已经是有效的系统FileType
            try:
                FileType(original_type)
                logger.info(f"文件 {file_input.file_name} 类型已是系统FileType: {original_type}")
                continue
            except ValueError:
                # 不是系统FileType，需要分类
                logger.info(f"文件 {file_input.file_name} 需要分类，原始类型: {original_type}")
            
            # 检查是否需要LLM分类（一对多关系，如QTWJ）
            require_llm = original_type in FileClassificationService.REQUIRE_LLM_CLASSIFICATION
            
            if require_llm:
                logger.info(f"文件 {file_input.file_name} 的中台类型 {original_type} 需要LLM智能分类")
                files_need_llm.append(file_input)
            else:
                # 先尝试直接映射（快速路径）
                classification_result = await classification_service.classify_file(
                    file_name=file_input.file_name,
                    original_type_code=original_type,
                    file_content=None
                )
                
                # 如果直接映射成功，使用映射结果
                if classification_result["method"] == "direct_mapping":
                    classified_type = classification_result["file_type"]
                    file_input.file_type = classified_type
                    logger.info(
                        f"文件 {file_input.file_name} 直接映射成功: "
                        f"{original_type} -> {classified_type}"
                    )
                else:
                    # 无法直接映射，也需要LLM分类
                    files_need_llm.append(file_input)
        
        # 第二阶段：批量解析需要LLM分类的文件
        # 用于缓存已解析的文件，避免后续重复解析
        parsed_files_cache = {}
        
        if not files_need_llm:
            logger.info("没有需要LLM分类的文件")
            return parsed_files_cache
        
        logger.info(f"批量解析 {len(files_need_llm)} 个需要LLM分类的文件")
        
        # 并行解析所有需要分类的文件
        async def parse_file_for_classification(file_input):
            """解析单个文件"""
            try:
                extraction_file_input = ExtractionFileInput(
                    file_url=file_input.file_url,
                    file_name=file_input.file_name,
                    file_type=FileType.INFO_DISCLOSURE_APPLICATION
                )
                
                parse_result = await self.extraction_service._parse_single_file(extraction_file_input)
                
                if parse_result and hasattr(parse_result, 'md_content'):
                    logger.info(f"文件 {file_input.file_name} 解析成功，内容长度: {len(parse_result.md_content)}")
                    return {
                        "file_input": file_input,
                        "file_content": parse_result.md_content,
                        "parse_result": parse_result,  # 保存完整的解析结果用于缓存
                        "success": True
                    }
                else:
                    logger.warning(f"文件 {file_input.file_name} 解析失败")
                    return {
                        "file_input": file_input,
                        "file_content": None,
                        "parse_result": None,
                        "success": False
                    }
            except Exception as e:
                logger.error(f"文件 {file_input.file_name} 解析异常: {e}", exc_info=True)
                return {
                    "file_input": file_input,
                    "file_content": None,
                    "parse_result": None,
                    "success": False,
                    "error": str(e)
                }
        
        # 并行解析所有文件
        parse_tasks = [parse_file_for_classification(f) for f in files_need_llm]
        parse_results = await asyncio.gather(*parse_tasks, return_exceptions=True)
        
        # 保存解析结果到缓存（按 file_url 索引）
        for result in parse_results:
            if not isinstance(result, Exception) and result.get("success") and result.get("parse_result"):
                file_url = result["file_input"].file_url
                parsed_files_cache[file_url] = result["parse_result"]
                logger.debug(f"已缓存文件解析结果: {file_url}")
        
        # 第三阶段：并行调用LLM进行分类
        logger.info(f"并行调用LLM分类 {len(parse_results)} 个文件")
        
        # 创建分类任务
        async def classify_single_file(parse_result):
            """分类单个文件"""
            if isinstance(parse_result, Exception):
                logger.error(f"解析任务异常: {parse_result}")
                return None
            
            file_input = parse_result["file_input"]
            file_content = parse_result["file_content"]
            original_type = file_input.file_type
            
            try:
                classification_result = await classification_service.classify_file(
                    file_name=file_input.file_name,
                    original_type_code=original_type,
                    file_content=file_content
                )
                
                return {
                    "file_input": file_input,
                    "original_type": original_type,
                    "classification_result": classification_result,
                    "success": True
                }
            except Exception as e:
                logger.error(f"文件 {file_input.file_name} LLM分类异常: {e}", exc_info=True)
                return {
                    "file_input": file_input,
                    "original_type": original_type,
                    "success": False,
                    "error": str(e)
                }
        
        # 使用信号量控制并发数
        max_workers = self.concurrency_config.classification_workers
        semaphore = asyncio.Semaphore(max_workers)
        
        async def classify_with_semaphore(parse_result):
            async with semaphore:
                return await classify_single_file(parse_result)
        
        # 并行分类
        classification_tasks = [classify_with_semaphore(r) for r in parse_results]
        classification_results = await asyncio.gather(*classification_tasks, return_exceptions=True)
        
        # 第四阶段：更新文件类型
        for result in classification_results:
            if isinstance(result, Exception):
                logger.error(f"分类任务异常: {result}")
                continue
            
            if not result or not result.get("success"):
                continue
            
            file_input = result["file_input"]
            original_type = result["original_type"]
            classification_result = result["classification_result"]
            
            classified_type = classification_result["file_type"]
            file_input.file_type = classified_type
            
            logger.info(
                f"文件 {file_input.file_name} LLM分类完成: "
                f"{original_type} -> {classified_type} "
                f"(方法: {classification_result['method']}, "
                f"置信度: {classification_result['confidence']}, "
                f"原因: {classification_result.get('reason', '未提供')})"
            )
        
        logger.info(f"文件分类完成，共处理 {len(files_need_llm)} 个文件")
        logger.info(f"已缓存 {len(parsed_files_cache)} 个文件的解析结果，可复用于提取阶段")
        
        return parsed_files_cache
    
    async def _load_rule_config(self, request: AuditRequest):
        """加载规则配置"""
        if request.rule_config_id:
            logger.info(f"加载规则配置: {request.rule_config_id}")
            config = self.rule_service.get_config(request.rule_config_id)
            if not config:
                raise ValueError(f"规则配置不存在: {request.rule_config_id}")
            return config
        else:
            # 根据audit_type查找默认规则配置
            logger.info(f"未提供rule_config_id，尝试根据audit_type查找默认规则: {request.audit_type}")
            business_type = map_extraction_type_to_business_type(request.audit_type)
            
            if business_type is None:
                raise ValueError(
                    f"无法识别的audit_type: {request.audit_type}。"
                    f"支持的类型: 有纸大宗实物, 无纸大宗实物, 有纸小宗实物, 无纸小宗实物"
                )
            
            config = self.rule_service.get_default_config_by_business_type(business_type)
            
            if config is None:
                raise ValueError(
                    f"未找到业务类型 '{business_type.value}' 的启用规则配置。"
                    f"请先在规则配置管理中创建并启用对应的规则配置，或者显式提供rule_config_id参数。"
                )
            
            return config
    
    async def _get_extraction_results(
        self,
        request: AuditRequest,
        config_id: str,
        enable_cache: bool,
        files_hash: Optional[str],
        parsed_files_cache: Optional[Dict[str, Any]] = None
    ) -> Tuple[Dict[str, Any], List[FieldExtractionResult], Dict[str, Any]]:
        """
        获取提取结果（缓存或新提取）
        
        Args:
            request: 审核请求
            config_id: 规则配置ID
            enable_cache: 是否启用缓存
            files_hash: 文件哈希
            parsed_files_cache: 预解析的文件缓存（来自分类阶段），避免重复解析
            
        Returns:
            Tuple: (提取字段结果, 完整提取结果列表, 解析文件快照)
                - 提取字段结果: {field_name: {source_file_type: {"value": str, ...}}}（用于LLM审核）
                - 完整提取结果列表: List[FieldExtractionResult]（用于嵌入审核返回）
                - 解析文件快照: {file_type: {"file_names": [...], "file_label": str, "md_content": str}}
        """
        # 尝试从缓存中获取提取结果
        if enable_cache and files_hash:
            cached_task = await self.task_store.find_task_by_files_hash(
                files_hash=files_hash,
                task_type=TaskType.EXTRACTION,
                only_completed=True
            )
            
            if cached_task and cached_task.result:
                logger.info(f"找到缓存的提取结果，任务ID: {cached_task.task_id}")
                cached_result = cached_task.result if isinstance(cached_task.result, dict) else {}
                parsed_results = self._parse_extraction_result(cached_result)
                parsed_files = cached_result.get("parsed_files") or {}
                
                # 从缓存结果中恢复 FieldExtractionResult 列表
                results_list = []
                results_data = cached_result.get("results", [])
                for result_dict in results_data:
                    try:
                        results_list.append(FieldExtractionResult(**result_dict))
                    except Exception as e:
                        logger.warning(f"解析缓存的提取结果失败: {e}")
                
                return parsed_results, results_list, parsed_files
        
        # 没有缓存，执行新的提取
        if parsed_files_cache:
            logger.info(f"没有缓存的提取结果，执行新的提取（复用已解析的 {len(parsed_files_cache)} 个文件）")
        else:
            logger.info("没有缓存的提取结果，执行新的提取")
        
        extraction_request = ExtractionRequest(
            extraction_type=request.audit_type,
            files=[
                ExtractionFileInput(
                    file_url=f.file_url,
                    file_name=f.file_name,
                    file_type=f.file_type
                )
                for f in request.files
            ],
            rule_config_id=config_id
        )
        
        extraction_data = await self.extraction_service.extract_fields(
            request=extraction_request,
            enable_cache=enable_cache,
            parsed_files_cache=parsed_files_cache
        )
        
        raw_data = extraction_data.model_dump()
        parsed_results = self._parse_extraction_result(raw_data)
        parsed_files = raw_data.get("parsed_files") or extraction_data.parsed_files or {}
        
        # 直接使用 extraction_data.results，这已经是 List[FieldExtractionResult]
        results_list = extraction_data.results if extraction_data.results else []
        
        return parsed_results, results_list, parsed_files
    
    def _parse_extraction_result(self, extraction_result: Dict[str, Any]) -> Dict[str, Any]:
        """
        解析提取结果为便于审核使用的格式
        
        Args:
            extraction_result: 提取结果（ExtractionData的dict形式）
            
        Returns:
            Dict: {
                field_name: {
                    source_file_type: {
                        "value": extracted_value,
                        "source_file_name": file_name
                    }
                }
            }
        """
        parsed = {}
        
        results = extraction_result.get("results", [])
        for field_result in results:
            field_name = field_result.get("field_name")
            values = field_result.get("values", [])
            
            field_data = {}
            for value in values:
                source_file_type = value.get("source_file_type")
                extracted_value = value.get("extracted_value")
                source_file_name = value.get("source_file_name")
                
                field_data[source_file_type] = {
                    "value": extracted_value,
                    "source_file_name": source_file_name
                }
            
            parsed[field_name] = field_data
        
        logger.debug(f"解析提取结果: {list(parsed.keys())}")
        return parsed
    
    def _build_confirmed_fields_map(self, confirmed_fields: List) -> Dict[str, Any]:
        """构建用户确认字段映射"""
        return {
            field.field_name: field.field_value
            for field in confirmed_fields
        }
    
    def _convert_field_value_to_string(self, value: Any) -> str:
        """
        将字段值转换为字符串（用于 LLM 输入）
        
        支持各种 JSON 类型：
        - 字符串：直接返回
        - 数字/布尔值：转为字符串
        - 列表：如果元素是字符串，用逗号连接；否则转为 JSON 格式
        - 对象/字典：转为 JSON 格式
        - None：返回空字符串
        
        Args:
            value: 任意类型的字段值
            
        Returns:
            str: 转换后的字符串
        """
        if value is None:
            return ""
        
        if isinstance(value, str):
            return value
        
        if isinstance(value, (int, float, bool)):
            return str(value)
        
        if isinstance(value, list):
            # 如果列表元素都是简单字符串，用逗号连接（保持向后兼容）
            if all(isinstance(item, str) for item in value):
                return ", ".join(value)
            # 否则转为 JSON 格式
            return json.dumps(value, ensure_ascii=False, indent=None)
        
        if isinstance(value, dict):
            # 对象转为 JSON 格式
            return json.dumps(value, ensure_ascii=False, indent=None)
        
        # 其他类型（如自定义对象），尝试 JSON 序列化，失败则用 str()
        try:
            return json.dumps(value, ensure_ascii=False, indent=None)
        except (TypeError, ValueError):
            return str(value)

    def _normalize_material_name(self, name: Optional[str]) -> str:
        """规范化材料名称，便于匹配"""
        if not name:
            return ""

        normalized = name.lower().strip()
        normalized = re.sub(r"[《》\"'\s]", "", normalized)
        normalized = re.sub(r"\.(pdf|docx|doc|txt|rtf|png|jpg|jpeg|bmp|tiff|webp)$", "", normalized)
        return normalized

    def _build_material_lookup(self, files: List[AuditFileInput]) -> Dict[str, str]:
        """构建材料名称到文件类型的映射"""
        lookup: Dict[str, str] = {}

        for file_type_enum, label in FILE_TYPE_LABELS.items():
            normalized_label = self._normalize_material_name(label)
            lookup[normalized_label] = file_type_enum.value

        for file_input in files:
            file_type_value = file_input.file_type.value if isinstance(file_input.file_type, FileType) else str(file_input.file_type)
            lookup[file_type_value] = file_type_value
            normalized_name = self._normalize_material_name(file_input.file_name)
            lookup[normalized_name] = file_type_value

        return lookup

    def _get_file_label(self, file_type: Optional[str]) -> str:
        """根据文件类型获取展示名称"""
        if not file_type:
            return "未知材料"

        try:
            enum_value = FileType(file_type)
            return FILE_TYPE_LABELS.get(enum_value, file_type)
        except ValueError:
            return file_type

    def _resolve_file_type(
        self,
        material_name: Optional[str],
        material_lookup: Dict[str, str],
        parsed_files: Dict[str, Any]
    ) -> Optional[str]:
        """根据材料名称解析对应的文件类型"""
        if not material_name:
            return None

        normalized = self._normalize_material_name(material_name)

        if normalized in material_lookup:
            return material_lookup[normalized]

        if material_name in material_lookup:
            return material_lookup[material_name]

        # 尝试通过已解析文件的标签或文件名匹配
        for file_type, data in parsed_files.items():
            label = data.get("file_label")
            if label and self._normalize_material_name(label) == normalized:
                return file_type
            for file_name in data.get("file_names", []):
                if self._normalize_material_name(file_name) == normalized:
                    return file_type

        return None

    def _get_display_name(
        self,
        material_name: Optional[str],
        file_type: Optional[str],
        file_data: Optional[Dict[str, Any]]
    ) -> str:
        """获取用于展示的材料名称"""
        if material_name:
            return material_name

        if file_data:
            if file_data.get("file_label"):
                return file_data["file_label"]
            file_names = file_data.get("file_names")
            if file_names:
                return file_names[0]

        return self._get_file_label(file_type)

    def _get_required_file_types(
        self,
        audit_rules: List[AuditRule],
        material_lookup: Dict[str, str],
        parsed_files: Dict[str, Any]
    ) -> Set[str]:
        """获取需要解析全文的文件类型集合"""
        required: Set[str] = set()

        for rule in audit_rules:
            if not rule.enabled:
                continue

            if rule.audit_type == AuditType.FILE_AUDIT:
                if rule.audit_data_sources:
                    for ds in rule.audit_data_sources:
                        file_type = self._resolve_file_type(ds.material_name, material_lookup, parsed_files)
                        if file_type:
                            required.add(file_type)

            if rule.audit_type == AuditType.FIELD_AUDIT and rule.mapping_type == MappingType.MANUAL:
                if rule.audit_data_sources:
                    for ds in rule.audit_data_sources:
                        if ds.field_name:
                            continue
                        file_type = self._resolve_file_type(ds.material_name, material_lookup, parsed_files)
                        if file_type:
                            required.add(file_type)

        return required

    async def _parse_required_files(
        self,
        request: AuditRequest,
        missing_file_types: Set[str]
    ) -> Dict[str, Any]:
        """按需解析缺失的文件类型，复用提取服务的解析能力"""
        if not missing_file_types:
            return {}

        files_to_parse: List[ExtractionFileInput] = []
        for file_input in request.files:
            file_type_value = file_input.file_type.value if isinstance(file_input.file_type, FileType) else str(file_input.file_type)
            if file_type_value in missing_file_types:
                files_to_parse.append(
                    ExtractionFileInput(
                        file_url=file_input.file_url,
                        file_name=file_input.file_name,
                        file_type=file_input.file_type
                    )
                )

        if not files_to_parse:
            logger.warning("缺失的文件类型在请求中不存在，无法解析")
            return {}

        logger.info(f"按需解析 {len(files_to_parse)} 个文件用于全文审核: {missing_file_types}")
        parsed_raw = await self.extraction_service._parse_files(files_to_parse)
        snapshot = self.extraction_service.build_parsed_files_snapshot(parsed_raw)

        return {file_type: snapshot[file_type] for file_type in snapshot if file_type in missing_file_types}
    
    async def _audit_all_rules(
        self,
        audit_rules: List[AuditRule],
        extraction_results: Dict[str, Any],
        extraction_results_list: List[FieldExtractionResult],
        confirmed_fields_map: Dict[str, Any],
        parsed_files: Dict[str, Any],
        material_lookup: Dict[str, str],
        request: AuditRequest,
        task_id: Optional[str] = None,
        task_created: bool = False
    ) -> List[AuditRuleResult]:
        """
        审核所有规则
        
        使用并发方式审核多个规则，并实时更新任务进度
        """
        # 筛选启用的规则
        enabled_rules = [rule for rule in audit_rules if rule.enabled]
        total_rules = len(enabled_rules)
        
        if total_rules == 0:
            return []
        
        # 创建审核任务列表
        tasks = []
        
        for rule in enabled_rules:
            task = self._audit_single_rule(
                rule=rule,
                extraction_results=extraction_results,
                extraction_results_list=extraction_results_list,
                confirmed_fields_map=confirmed_fields_map,
                parsed_files=parsed_files,
                material_lookup=material_lookup,
                request=request
            )
            tasks.append(task)
        
        # 并发执行，限制并发数，同时追踪完成进度
        semaphore = asyncio.Semaphore(self.concurrency_config.audit_workers)
        
        async def bounded_task(idx: int, task):
            """包装任务，返回索引和结果"""
            async with semaphore:
                result = await task
                return idx, result
        
        # 创建带索引的任务
        bounded_tasks = [
            bounded_task(idx, task) 
            for idx, task in enumerate(tasks)
        ]
        
        # 使用 as_completed 追踪任务完成情况
        results = [None] * total_rules  # 预分配结果列表
        completed_count = 0
        
        # 逐个等待任务完成，实时更新进度
        for coro in asyncio.as_completed(bounded_tasks):
            idx, result = await coro
            results[idx] = result
            completed_count += 1
            
            # 更新任务进度
            if task_created and task_id:
                progress = completed_count / total_rules
                await self.task_store.update_task(task_id, progress=progress)
                logger.debug(f"审核进度更新: {completed_count}/{total_rules} ({progress:.2%})")
        
        return results
    
    async def _audit_single_rule(
        self,
        rule: AuditRule,
        extraction_results: Dict[str, Any],
        extraction_results_list: List[FieldExtractionResult],
        confirmed_fields_map: Dict[str, Any],
        parsed_files: Dict[str, Any],
        material_lookup: Dict[str, str],
        request: AuditRequest
    ) -> AuditRuleResult:
        """
        审核单个规则
        
        核心逻辑：
        1. 根据audit_type、mapping_type构建audit_data_sources（描述性）和reference_materials（实际内容）
        2. 预检查审核条件，如果不满足则直接返回待确认结果，跳过LLM调用
        3. 如果启用简单对比模式，直接进行字符串对比；否则调用LLM审核
        4. 调用LLM审核（如果满足条件）
        5. 封装结果
        """
        try:
            logger.info(f"审核规则: {rule.field_name} (简单对比模式: {rule.use_simple_comparison})")
            
            # 1. 构建审核数据源（描述性）和参考材料（实际内容）
            audit_data_sources = []  # 改为列表，只描述数据源
            reference_materials = {}  # 实际数据内容
            
            if rule.audit_type == AuditType.FIELD_AUDIT:
                # 字段审核
                if rule.mapping_type == MappingType.AUTO:
                    # 自动映射：从提取结果中根据source_field_name获取
                    source_field_name = rule.source_field_name
                    if source_field_name and source_field_name in extraction_results:
                        field_data = extraction_results[source_field_name]
                        for source_type, data in field_data.items():
                            file_name = data['source_file_name']
                            # audit_data_sources：只描述数据源
                            audit_data_sources.append(f"{file_name} - {source_field_name}")
                            # reference_materials：实际内容
                            key = f"{file_name} - {source_field_name}"
                            reference_materials[key] = data["value"]
                else:
                    # 自定义映射：根据audit_data_sources配置获取
                    if rule.audit_data_sources:
                        for ds in rule.audit_data_sources:
                            material_name = ds.material_name
                            field_name = ds.field_name
                            
                            if field_name:
                                # 字段值：从提取结果获取
                                if field_name in extraction_results:
                                    field_data = extraction_results[field_name]
                                    for source_type, data in field_data.items():
                                        # audit_data_sources：只描述
                                        audit_data_sources.append(f"{material_name} - {field_name}")
                                        # reference_materials：实际内容
                                        key = f"{material_name} - {field_name}"
                                        reference_materials[key] = data["value"]
                                else:
                                    logger.warning(f"未找到字段提取结果: {field_name}")
                            else:
                                # 全文：从parsed_files获取
                                file_type = self._resolve_file_type(material_name, material_lookup, parsed_files)
                                file_data = parsed_files.get(file_type) if file_type else None

                                if file_data and file_data.get("md_content"):
                                    display_name = self._get_display_name(material_name, file_type, file_data)
                                    key = f"{display_name} - 全文"
                                    audit_data_sources.append(key)
                                    
                                    # === RAG 处理（字段审核-全文模式）===
                                    md_content = file_data.get("md_content", "")
                                    if self.rag_handler.should_use_rag(len(md_content)):
                                        logger.info(f"审核全文内容过长 ({len(md_content)} 字符)，启用 RAG 检索")
                                        parse_results = file_data.get("parse_results", [])
                                        # 使用审核规则的字段名和审核内容作为查询
                                        query = f"{rule.field_name} {rule.audit_content}"
                                        md_content, _ = self.rag_handler.get_relevant_content(
                                            parse_results, query
                                        )
                                        logger.info(f"RAG 检索后内容长度: {len(md_content)} 字符")
                                    # =====================================
                                    
                                    reference_materials[key] = md_content
                                else:
                                    logger.warning(f"未找到全文解析内容: material={material_name}, file_type={file_type}")
                
                # 是否包含用户输入
                user_input_value = None
                if rule.include_user_input and rule.field_name in confirmed_fields_map:
                    user_input_value = confirmed_fields_map[rule.field_name]
                    # 转换为字符串（支持各种 JSON 类型）
                    user_input_value = self._convert_field_value_to_string(user_input_value)
            
            else:
                # 文件审核：根据audit_data_sources获取全文
                user_input_value = None
                if rule.audit_data_sources:
                    for ds in rule.audit_data_sources:
                        material_name = ds.material_name
                        file_type = self._resolve_file_type(material_name, material_lookup, parsed_files)
                        file_data = parsed_files.get(file_type) if file_type else None

                        if file_data and file_data.get("md_content"):
                            display_name = self._get_display_name(material_name, file_type, file_data)
                            key = f"{display_name} - 全文"
                            audit_data_sources.append(key)
                            
                            # === RAG 处理（文件审核）===
                            md_content = file_data.get("md_content", "")
                            if self.rag_handler.should_use_rag(len(md_content)):
                                logger.info(f"文件审核内容过长 ({len(md_content)} 字符)，启用 RAG 检索")
                                parse_results = file_data.get("parse_results", [])
                                # 使用审核内容作为查询（文件审核通常审核特定要点）
                                query = f"{rule.field_name} {rule.audit_content}"
                                md_content, _ = self.rag_handler.get_relevant_content(
                                    parse_results, query
                                )
                                logger.info(f"RAG 检索后内容长度: {len(md_content)} 字符")
                            # ==============================
                            
                            reference_materials[key] = md_content
                        else:
                            logger.warning(f"未找到文件审核所需的全文内容: material={material_name}, file_type={file_type}")
            
            # 2. 预检查审核条件
            pre_check_result = self._pre_check_audit_conditions(
                rule=rule,
                user_input_value=user_input_value,
                reference_materials=reference_materials,
                extraction_results_list=extraction_results_list
            )
            
            if pre_check_result:
                # 如果预检查返回结果，说明不满足审核条件，直接返回
                logger.info(f"规则 {rule.field_name} 预检查未通过，跳过LLM审核")
                return pre_check_result
            
            # 3. 判断是否使用简单对比模式
            if rule.use_simple_comparison:
                # 简单对比模式：直接进行字符串对比
                comparison_result = self._simple_field_comparison(
                    user_input_value=user_input_value,
                    reference_materials=reference_materials
                )
                
                # 构建字段信息（包含完整的提取数据）
                extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                related_fields = [RelatedFieldInfo(
                    field_name=rule.field_name,
                    field_value=user_input_value or "",
                    field_type="text",
                    extraction_data=extraction_data
                )]
                
                # 构建审核规则信息
                audit_rule_info = self._build_audit_rule_info(rule)
                
                return AuditRuleResult(
                    audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                    audit_rule_id=getattr(rule, 'id', None),
                    audit_rule_name=rule.field_name,
                    audit_type=AuditTypeEnum.FIELD_AUDIT,
                    audit_result=comparison_result["audit_result"],
                    audit_status=AuditStatus.COMPLETED,
                    audit_explanation=comparison_result["explanation"],
                    confidence=comparison_result["confidence"],
                    related_fields=related_fields,
                    audit_rule=audit_rule_info
                )
            
            # 4. 调用LLM审核
            audit_config = self.llm_config.get_audit_agent_config()
            
            async with LLMClient(audit_config) as client:
                agent = AuditAgent(client)
                llm_result = await agent.audit_field(
                    audit_type=rule.audit_type.value,
                    field_name=rule.field_name,
                    audit_content=rule.audit_content,
                    auto_process_rules=rule.auto_process_rules,
                    audit_data_sources=audit_data_sources,
                    user_input_value=user_input_value,
                    reference_materials=reference_materials
                )
            
            # 5. 封装结果
            if llm_result.success:
                # 映射审核结果
                audit_result_str = llm_result.audit_result
                if audit_result_str == "通过":
                    audit_result = AuditResultEnum.PASSED
                elif audit_result_str == "不通过":
                    audit_result = AuditResultEnum.FAILED
                else:
                    audit_result = AuditResultEnum.PENDING
                
                # 构建审核规则信息
                audit_rule_info = self._build_audit_rule_info(rule)
                
                # 根据审核类型构建不同的返回结构
                if rule.audit_type == AuditType.FIELD_AUDIT:
                    # 字段审核：构建 related_fields（包含完整的提取数据）
                    extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                    related_fields = [RelatedFieldInfo(
                        field_name=rule.field_name,
                        field_value=user_input_value or "",
                        field_type="text",
                        extraction_data=extraction_data
                    )]
                    
                    return AuditRuleResult(
                        audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                        audit_rule_id=getattr(rule, 'id', None),
                        audit_rule_name=rule.field_name,
                        audit_type=AuditTypeEnum.FIELD_AUDIT,
                        audit_result=audit_result,
                        audit_status=AuditStatus.COMPLETED,
                        audit_explanation=llm_result.audit_analysis,
                        confidence=0.9,
                        related_fields=related_fields,
                        audit_rule=audit_rule_info
                    )
                else:
                    # 文件审核：构建文件审核专用字段
                    # 1. 获取 material_name 作为 file_type
                    file_type = None
                    if rule.audit_data_sources and len(rule.audit_data_sources) > 0:
                        file_type = rule.audit_data_sources[0].material_name
                    
                    # 2. 匹配已上传的文件
                    uploaded_files = self._match_uploaded_files(file_type, request.files)
                    
                    return AuditRuleResult(
                        audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                        audit_rule_id=getattr(rule, 'id', None),
                        audit_rule_name=rule.field_name,
                        audit_type=AuditTypeEnum.FILE_AUDIT,
                        audit_result=audit_result,
                        audit_status=AuditStatus.COMPLETED,
                        audit_explanation=llm_result.audit_analysis,
                        confidence=0.9,
                        related_fields=[],  # 文件审核不关联字段
                        file_type=file_type,
                        audit_points=rule.audit_content,
                        uploaded_files=uploaded_files,
                        audit_details=llm_result.audit_analysis,
                        audit_rule=audit_rule_info
                    )
            else:
                # 审核失败
                related_fields = []
                if rule.audit_type == AuditType.FIELD_AUDIT:
                    extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                    related_fields = [RelatedFieldInfo(
                        field_name=rule.field_name,
                        field_value=user_input_value or "",
                        field_type="text",
                        extraction_data=extraction_data
                    )]
                
                # 构建审核规则信息
                audit_rule_info = self._build_audit_rule_info(rule)
                
                return AuditRuleResult(
                    audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                    audit_rule_id=getattr(rule, 'id', None),
                    audit_rule_name=rule.field_name,
                    audit_type=AuditTypeEnum.FIELD_AUDIT if rule.audit_type == AuditType.FIELD_AUDIT else AuditTypeEnum.FILE_AUDIT,
                    audit_result=AuditResultEnum.PENDING,
                    audit_status=AuditStatus.FAILED,
                    audit_explanation=f"审核失败: {llm_result.error}",
                    confidence=0.0,
                    related_fields=related_fields,
                    audit_rule=audit_rule_info
                )
        
        except Exception as e:
            logger.error(f"审核规则 {rule.field_name} 失败: {e}", exc_info=True)
            
            # 根据审核类型构建不同的返回结构
            related_fields = []
            if rule.audit_type == AuditType.FIELD_AUDIT:
                user_input_value = None
                if rule.include_user_input and rule.field_name in confirmed_fields_map:
                    user_input_value = confirmed_fields_map[rule.field_name]
                    # 转换为字符串（支持各种 JSON 类型）
                    user_input_value = self._convert_field_value_to_string(user_input_value)
                
                extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                related_fields = [RelatedFieldInfo(
                    field_name=rule.field_name,
                    field_value=user_input_value or "",
                    field_type="text",
                    extraction_data=extraction_data
                )]
            
            # 构建审核规则信息（即使异常也要返回）
            audit_rule_info = self._build_audit_rule_info(rule)
            
            return AuditRuleResult(
                audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                audit_rule_id=getattr(rule, 'id', None),
                audit_rule_name=rule.field_name,
                audit_type=AuditTypeEnum.FIELD_AUDIT if rule.audit_type == AuditType.FIELD_AUDIT else AuditTypeEnum.FILE_AUDIT,
                audit_result=AuditResultEnum.PENDING,
                audit_status=AuditStatus.FAILED,
                audit_explanation=f"审核异常: {str(e)}",
                confidence=0.0,
                related_fields=related_fields,
                audit_rule=audit_rule_info
            )
    
    def _pre_check_audit_conditions(
        self,
        rule: AuditRule,
        user_input_value: Optional[str],
        reference_materials: Dict[str, str],
        extraction_results_list: List[FieldExtractionResult]
    ) -> Optional[AuditRuleResult]:
        """
        预检查审核条件，判断是否可以跳过LLM调用
        
        检查以下条件：
        1. 缺少参考材料
        2. 字段审核时要求用户输入但用户未提供（文件审核不检查此条件）
        3. 参考材料中所有值都为空
        
        Args:
            rule: 审核规则
            user_input_value: 用户输入的字段值（文件审核时为None）
            reference_materials: 参考材料字典 {材料名称: 字段值或全文}
            
        Returns:
            - None: 满足审核条件，可以继续调用LLM
            - AuditRuleResult: 不满足条件，返回待确认结果
        """
        
        # 条件1: 缺少参考材料
        if not reference_materials or len(reference_materials) == 0:
            logger.warning(f"规则 {rule.field_name} 缺少参考材料，跳过LLM审核")
            
            related_fields = []
            if rule.audit_type == AuditType.FIELD_AUDIT:
                extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                related_fields = [RelatedFieldInfo(
                    field_name=rule.field_name,
                    field_value=user_input_value or "",
                    field_type="text",
                    extraction_data=extraction_data
                )]
            
            # 构建审核规则信息
            audit_rule_info = self._build_audit_rule_info(rule)
            
            return AuditRuleResult(
                audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                audit_rule_id=getattr(rule, 'id', None),
                audit_rule_name=rule.field_name,
                audit_type=AuditTypeEnum.FIELD_AUDIT if rule.audit_type == AuditType.FIELD_AUDIT else AuditTypeEnum.FILE_AUDIT,
                audit_result=AuditResultEnum.PENDING,
                audit_status=AuditStatus.COMPLETED,
                audit_explanation="缺少必要的参考材料，无法进行审核。请确保已上传相关文件并成功解析。",
                confidence=0.0,
                related_fields=related_fields,
                audit_rule=audit_rule_info
            )
        
        # 条件2: 字段审核时要求用户输入但用户未提供
        # 注意：文件审核不需要用户输入字段值，user_input_value 为 None 是正常的
        if rule.audit_type == AuditType.FIELD_AUDIT and rule.include_user_input and (not user_input_value or not user_input_value.strip()):
            logger.warning(f"规则 {rule.field_name} 要求用户输入但未提供，跳过LLM审核")
            extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
            
            # 构建审核规则信息
            audit_rule_info = self._build_audit_rule_info(rule)
            
            return AuditRuleResult(
                audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                audit_rule_id=getattr(rule, 'id', None),
                audit_rule_name=rule.field_name,
                audit_type=AuditTypeEnum.FIELD_AUDIT,
                audit_result=AuditResultEnum.PENDING,
                audit_status=AuditStatus.COMPLETED,
                audit_explanation="该审核项要求用户输入字段值，但未提供。请先完成字段录入。",
                confidence=0.0,
                related_fields=[RelatedFieldInfo(
                    field_name=rule.field_name,
                    field_value="",
                    field_type="text",
                    extraction_data=extraction_data
                )],
                audit_rule=audit_rule_info
            )
        
        # 条件3: 参考材料中所有值都为空
        all_empty = all(
            not value or not str(value).strip() 
            for value in reference_materials.values()
        )
        if all_empty:
            logger.warning(f"规则 {rule.field_name} 的所有参考材料值为空，跳过LLM审核")
            
            related_fields = []
            if rule.audit_type == AuditType.FIELD_AUDIT:
                extraction_data = self._find_extraction_data(rule.field_name, extraction_results_list)
                related_fields = [RelatedFieldInfo(
                    field_name=rule.field_name,
                    field_value=user_input_value or "",
                    field_type="text",
                    extraction_data=extraction_data
                )]
            
            # 构建审核规则信息
            audit_rule_info = self._build_audit_rule_info(rule)
            
            return AuditRuleResult(
                audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                audit_rule_id=getattr(rule, 'id', None),
                audit_rule_name=rule.field_name,
                audit_type=AuditTypeEnum.FIELD_AUDIT if rule.audit_type == AuditType.FIELD_AUDIT else AuditTypeEnum.FILE_AUDIT,
                audit_result=AuditResultEnum.PENDING,
                audit_status=AuditStatus.COMPLETED,
                audit_explanation="参考材料中未提取到有效内容，无法进行审核。建议检查文件质量或重新上传。",
                confidence=0.0,
                related_fields=related_fields,
                audit_rule=audit_rule_info
            )
        
        # 所有条件通过，可以继续审核
        return None
    
    def _simple_field_comparison(
        self,
        user_input_value: Optional[str],
        reference_materials: Dict[str, str]
    ) -> Dict[str, Any]:
        """
        简单字段对比逻辑
        
        适用于简单的字段一致性校验，直接比对用户输入值与参考材料中的字段值。
        
        规则：
        1. 若用户输入为空或参考材料为空，则审核结果为待确认
        2. 若用户输入值与所有参考材料字段值一致，则审核通过
        3. 若用户输入值与任一参考材料字段值不一致，则审核不通过
        
        Args:
            user_input_value: 用户输入的字段值
            reference_materials: 参考材料字典 {材料名称: 字段值}
            
        Returns:
            Dict: {
                "audit_result": AuditResultEnum,
                "explanation": str,
                "confidence": float
            }
        """
        # 1. 检查数据完整性
        if not user_input_value or not user_input_value.strip():
            return {
                "audit_result": AuditResultEnum.PENDING,
                "explanation": "用户未输入字段值，无法进行对比审核",
                "confidence": 0.0
            }
        
        if not reference_materials:
            return {
                "audit_result": AuditResultEnum.PENDING,
                "explanation": "未找到参考材料中的字段值，无法进行对比审核",
                "confidence": 0.0
            }
        
        # 规范化用户输入（去除首尾空格，转小写）
        normalized_user_input = user_input_value.strip().lower()
        
        # 2. 对比所有参考材料
        all_matched = True
        mismatch_details = []
        match_details = []
        
        for material_name, material_value in reference_materials.items():
            if not material_value or not str(material_value).strip():
                # 参考材料值为空，标记为待确认
                return {
                    "audit_result": AuditResultEnum.PENDING,
                    "explanation": f"参考材料「{material_name}」中未找到字段值，无法进行对比审核",
                    "confidence": 0.0
                }
            
            # 规范化参考材料值
            normalized_material_value = str(material_value).strip().lower()
            
            # 进行对比
            if normalized_user_input == normalized_material_value:
                match_details.append(f"✓ 「{material_name}」: 一致")
            else:
                all_matched = False
                mismatch_details.append(
                    f"✗ 「{material_name}」: 用户输入「{user_input_value.strip()}」与材料中的「{str(material_value).strip()}」不一致"
                )
        
        # 3. 生成审核结果
        if all_matched:
            explanation = "字段值对比一致，审核通过。\n\n对比详情：\n" + "\n".join(match_details)
            return {
                "audit_result": AuditResultEnum.PASSED,
                "explanation": explanation,
                "confidence": 1.0
            }
        else:
            explanation = "字段值对比不一致，审核不通过。\n\n对比详情：\n"
            explanation += "\n".join(match_details + mismatch_details)
            return {
                "audit_result": AuditResultEnum.FAILED,
                "explanation": explanation,
                "confidence": 1.0
            }
    
    def _add_manual_review_items(
        self,
        confirmed_fields: List[ConfirmedField],
        audit_results: List[AuditRuleResult],
        extraction_results_list: List[FieldExtractionResult]
    ) -> List[AuditRuleResult]:
        """
        添加需人工审核的字段
        
        逻辑：
        1. 收集已审核的字段
        2. 找出 confirmed_fields 中未被审核的字段
        3. 为每个未审核的字段创建一个人工审核项，包含提取数据
        
        Args:
            confirmed_fields: 用户确认的字段列表
            audit_results: 审核结果列表
            extraction_results_list: 完整提取结果列表
            
        Returns:
            List[AuditRuleResult]: 包含人工审核项的审核结果列表
        """
        # 1. 收集已审核的字段
        audited_fields = set()
        for result in audit_results:
            for field_info in result.related_fields:
                audited_fields.add(field_info.field_name)
        
        # 2. 找出未审核的字段
        for field in confirmed_fields:
            if field.field_name not in audited_fields:
                logger.info(f"字段 '{field.field_name}' 未被自动审核覆盖，标记为需人工审核")
                
                # 3. 获取该字段的完整提取数据
                extraction_data = self._find_extraction_data(field.field_name, extraction_results_list)
                
                # 4. 创建人工审核项
                audit_results.append(AuditRuleResult(
                    audit_id=f"audit_{uuid.uuid4().hex[:16]}",
                    audit_rule_id=None,
                    audit_rule_name=field.field_name,
                    audit_type=AuditTypeEnum.MANUAL_REVIEW,
                    audit_result=AuditResultEnum.MANUAL_REVIEW,
                    audit_status=AuditStatus.COMPLETED,
                    audit_explanation="该字段未配置自动审核规则，需要人工审核",
                    confidence=None,
                    related_fields=[RelatedFieldInfo(
                        field_name=field.field_name,
                        field_value=field.field_value,
                        field_type=field.field_type,
                        extraction_data=extraction_data
                    )]
                ))
        
        return audit_results
    
    def _find_extraction_data(
        self,
        field_name: str,
        extraction_results_list: List[FieldExtractionResult]
    ) -> Optional[FieldExtractionResult]:
        """
        根据字段名查找对应的提取数据
        
        Args:
            field_name: 字段名称
            extraction_results_list: 提取结果列表
            
        Returns:
            Optional[FieldExtractionResult]: 找到的提取数据，未找到返回 None
        """
        for result in extraction_results_list:
            if result.field_name == field_name:
                return result
        return None
    
    def _match_uploaded_files(
        self,
        material_name: Optional[str],
        files: List[AuditFileInput]
    ) -> List[UploadedFileInfo]:
        """
        根据 material_name 匹配已上传的文件
        
        Args:
            material_name: 材料名称（如"《信息披露申请书》"）
            files: 请求中的文件列表
            
        Returns:
            List[UploadedFileInfo]: 匹配的文件列表
        """
        if not material_name:
            return []
        
        # 使用 LABEL_TO_FILE_TYPE 映射
        file_type_enum = LABEL_TO_FILE_TYPE.get(material_name)
        
        if not file_type_enum:
            logger.warning(f"无法将 material_name '{material_name}' 映射到文件类型")
            return []
        
        # 在请求文件中查找匹配的文件
        matched_files = []
        for f in files:
            file_type_value = f.file_type.value if isinstance(f.file_type, FileType) else str(f.file_type)
            if file_type_value == file_type_enum.value:
                matched_files.append(UploadedFileInfo(
                    file_name=f.file_name,
                    file_url=f.file_url,
                    file_type=file_type_value
                ))
        
        return matched_files
    
    def _build_audit_rule_info(self, rule: AuditRule) -> Dict[str, Any]:
        """
        构建审核规则信息（用于前端展示）
        
        Args:
            rule: 审核规则对象
            
        Returns:
            Dict: 审核规则信息字典
        """
        audit_data_sources_list = []
        if rule.audit_data_sources:
            for ds in rule.audit_data_sources:
                audit_data_sources_list.append({
                    "material_name": ds.material_name,
                    "field_name": ds.field_name
                })
        
        return {
            "rule_id": getattr(rule, 'id', None),
            "field_name": rule.field_name,
            "audit_type": rule.audit_type.value,
            "audit_content": rule.audit_content,
            "mapping_type": rule.mapping_type.value if rule.mapping_type else None,
            "source_field_name": rule.source_field_name,
            "include_user_input": rule.include_user_input,
            "use_simple_comparison": rule.use_simple_comparison,
            "auto_process_rules": rule.auto_process_rules,
            "audit_data_sources": audit_data_sources_list,
            "enabled": rule.enabled
        }


